#include "zjson.h"

#define PEEK(ptr) (*(ptr))
#define EAT(ptr) ptr++
#define ERR(ptr) do{ \
                    printf("Parse JSONPath error!\n\t%s\n\t" ,JSONPATH); \
                    for (int i = 0; i < ptr - JSONPATH; ++i) printf(" "); \
                    printf("^"); \
                    printf("\n\tCan't recognize '%c'\n",*(ptr)); \
                    exit(EXIT_FAILURE); \
                }while(0)

typedef enum {
    JpathStart,
    Dollar,
    Dot,
    LeftSB,
    RightSB
    /* Reserved states */
//    Index,
//    At,
//    Comma,
//    Question,
//    Key,
//    Quote,
//    Star,
//    Colon
} JpathState;

static JpathState state = JpathStart;
static const char *JSONPATH;

static int eat_int(const jchar **pp) {
    char buffer[41] = {0};
    char *ptr = buffer;
	int sign = 1;
    if (!((**pp >= '0' && **pp <= '9') || **pp == '-')) {
        ERR(*pp);
    }
	if (**pp == '-') {
		sign = -1;
		(*pp)++;
	}
    while (**pp >= '0' && **pp <= '9') {
        *ptr++ = *((*pp)++);
    }
    return sign * atoi(buffer);
}

static jstr eat_key(const jchar **pp) {
    jstr buffer = (jstr)malloc(1024);
    memset(buffer,0,1024);
    jchar *ptr = buffer;
    if (!(**pp != '.' && **pp != '[' && **pp != '\0')) {
        ERR(*pp);
    }
    while (**pp != '.' && **pp != '[' && **pp != '\0') {
        *ptr++ = *((*pp)++);
    }
    return buffer;
}

jval *zJSON_exec(jval *root, const char *path) {
    const char *p = path;
    JSONPATH = path;
    jval *currentNode = root;
    while (1)
        switch (PEEK(p)) {
            case '$':
                if (state == JpathStart) {//ex Dot or [
                    state = Dollar;
                    currentNode = root;
                    EAT(p);
                } else {
                    ERR(p);
                }
                break;
            case '.':
                if (state == Dollar
                    || state == Dot
                    || state == RightSB) {//ex Key or *
                    state = Dot;
                    EAT(p);
                    jstr key = eat_key(&p);
                    currentNode = zJSON_get_key_val(currentNode, key);
                    free(key);
                } else {
                    ERR(p);
                }
                break;
            case '[':
                if (state == Dot || state == Dollar) {//ex index and ']'
                    state = LeftSB;
                    EAT(p);
                    int idx = eat_int(&p);
                    currentNode = zJSON_get_arr_item(currentNode, idx);
                    if (*p == ']') {
                        state = RightSB;
                        EAT(p);
                    } else {
                        ERR(p);
                    }
                } else {
                    ERR(p);
                }
                break;
            case '\0':
                goto EXEC_END;
            default:
                ERR(p);
                break;
        }
    EXEC_END:
    return currentNode;
}
